package com.linkgie.galaxyframework.utils.json;

import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

public class JsonProxyAccesser implements InvocationHandler {

	private static Map<Class<?>, ValueConverter<?>> primativeConverters = new HashMap<>();

	static {
		addConverter(boolean.class, n -> n == null ? false : n.booleanValue());
		addConverter(Boolean.class, n -> n == null ? null : n.booleanValue());
		addConverter(boolean[].class, new JSONArrayConverter(boolean.class, n -> n.booleanValue()));
		addConverter(Boolean[].class, new JSONArrayConverter(Boolean.class, n -> n.booleanValue()));

		addConverter(byte.class, n -> n == null ? 0 : castToByte(n.intValue()));
		addConverter(Byte.class, n -> n == null ? null : castToByte(n.intValue()));
		addConverter(byte[].class, n -> {
			try {
				return n == null ? 0 : n.binaryValue();
			} catch (IOException e) {
				throw new JsonException(e.getMessage(), e);
			}
		});

		addConverter(short.class, n -> n == null ? 0 : castToShort(n.intValue()));
		addConverter(Short.class, n -> n == null ? null : castToShort(n.intValue()));
		addConverter(short[].class,
				new JSONArrayConverter(short.class, n -> n == null ? 0 : castToShort(n.intValue())));
		addConverter(Short[].class,
				new JSONArrayConverter(Short.class, n -> n == null ? null : castToShort(n.intValue())));

		addConverter(char.class, n -> n == null ? 0 : castToChar(n.intValue()));
		addConverter(Character.class, n -> n == null ? null : castToChar(n.intValue()));
		addConverter(char[].class, new JSONArrayConverter(char.class, n -> n == null ? 0 : castToChar(n.intValue())));
		addConverter(Character[].class,
				new JSONArrayConverter(Character.class, n -> n == null ? null : castToChar(n.intValue())));

		addConverter(int.class, n -> n == null ? 0 : n.intValue());
		addConverter(Integer.class, n -> n == null ? null : n.intValue());
		addConverter(int[].class, new JSONArrayConverter(int.class, n -> n == null ? 0 : n.intValue()));
		addConverter(Integer[].class, new JSONArrayConverter(Integer.class, n -> n == null ? null : n.intValue()));

		addConverter(long.class, n -> n == null ? 0L : n.longValue());
		addConverter(Long.class, n -> n == null ? null : n.longValue());
		addConverter(long[].class, new JSONArrayConverter(long.class, n -> n == null ? 0 : n.longValue()));
		addConverter(Long[].class, new JSONArrayConverter(Long.class, n -> n == null ? null : n.longValue()));

		addConverter(float.class, n -> n == null ? 0F : n.floatValue());
		addConverter(Float.class, n -> n == null ? null : n.floatValue());
		addConverter(float[].class, new JSONArrayConverter(float.class, n -> n == null ? 0D : n.floatValue()));
		addConverter(Float[].class, new JSONArrayConverter(Float.class, n -> n == null ? null : n.floatValue()));

		addConverter(double.class, n -> n == null ? 0D : n.doubleValue());
		addConverter(Double.class, n -> n == null ? null : n.doubleValue());
		addConverter(double[].class, new JSONArrayConverter(double.class, n -> n == null ? 0D : n.doubleValue()));
		addConverter(Double[].class, new JSONArrayConverter(Double.class, n -> n == null ? null : n.doubleValue()));

		addConverter(BigInteger.class, n -> n == null ? null : n.bigIntegerValue());
		addConverter(BigInteger[].class,
				new JSONArrayConverter(BigInteger.class, n -> n == null ? null : n.bigIntegerValue()));

		addConverter(BigDecimal.class, n -> n == null ? null : n.isBigDecimal());
		addConverter(BigDecimal[].class,
				new JSONArrayConverter(BigDecimal.class, n -> n == null ? null : n.isBigDecimal()));

		addConverter(String.class, n -> n == null ? null : n.textValue());
		addConverter(String[].class, new JSONArrayConverter(String.class, n -> n == null ? null : n.textValue()));
	}

	private static void addConverter(Class<?> type, ValueConverter<?> converter) {
		primativeConverters.put(type, converter);
	}

	private static byte castToByte(int v) {
		if (v > Byte.MAX_VALUE && v < Byte.MIN_VALUE) {
			throw new JsonException("Byte value overflow!");
		}
		return (byte) v;
	}

	private static short castToShort(int v) {
		if (v > Short.MAX_VALUE && v < Short.MIN_VALUE) {
			throw new JsonException("Short value overflow!");
		}
		return (short) v;
	}

	private static char castToChar(int v) {
		if (v > Character.MAX_VALUE && v < Character.MIN_VALUE) {
			throw new JsonException("Char value overflow!");
		}
		return (char) v;
	}

	private static final PropertyConverter NONE_PROP = new PropertyConverter(null, null);

	private ObjectMapper context;

	private Map<Method, PropertyConverter> propertyMathodMapping = new ConcurrentHashMap<>();

	private JsonNode jsonNode;

	private Method jsonValueMethod;

	public JsonProxyAccesser(JsonNode jsonNode, ObjectMapper context) {
		this.jsonNode = jsonNode;
		this.context = context;
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		if (isJsonValueMethod(method)) {
			return jsonNode;
		}
		PropertyConverter propConverter = propertyMathodMapping.get(method);
		if (propConverter == NONE_PROP) {
			throw new IllegalAccessException("No value provided for no pojo property method!");
		}
		if (propConverter == null) {
			String propertyName = resolvePropertyName(method);
			if (propertyName == null) {
				propertyMathodMapping.put(method, NONE_PROP);
				throw new IllegalAccessException("No value provided for no pojo property method!");
			}

			Class<?> valueType = method.getReturnType();
			ValueConverter<?> converter = primativeConverters.get(valueType);
			if (converter == null) {
				// 非基本类型；
				converter = new JsonObjectConverter(valueType, context);
			}

			propConverter = new PropertyConverter(propertyName, converter);
			propertyMathodMapping.put(method, propConverter);
		}

		JsonNode value = jsonNode.findValue(propConverter.PROPERTY_NAME);
		return propConverter.convert(value);
	}

	private boolean isJsonValueMethod(Method method) {
		if (jsonValueMethod == method) {
			return true;
		}
		if (jsonValueMethod == null && method.getParameterCount() == 0 && method.getDeclaringClass() == JsonProxy.class
				&& method.getReturnType() == JsonNode.class && method.getName().equals("jsonValue")) {
			jsonValueMethod = method;
			return true;
		}
		return false;
	}

	private static String resolvePropertyName(Method method) {
		if (method.getParameterCount() > 0) {
			// get 属性方法不能有参数；
			return null;
		}
		String methodName = method.getName();
		Class<?> valueType = method.getReturnType();
		if (valueType == void.class || valueType == Void.class) {
			return null;
		}
		if (valueType == boolean.class) {
			// boolean 类型的属性以 is 为前缀，且前缀后的第一个字母为大写；
			if (methodName.length() > 2 && methodName.startsWith("is") && Character.isUpperCase(methodName.charAt(2))) {
				return Character.toLowerCase(methodName.charAt(2)) + methodName.substring(3);
			}
			return null;
		}

		if (methodName.length() > 3 && methodName.startsWith("get") && Character.isUpperCase(methodName.charAt(3))) {
			if ("getClass".equals(methodName)) {
				// 忽略 Object.getClass() 方法；
				return null;
			}
			String propertyName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);
			if ("iD".equals(propertyName)) {
				return "id";
			}
			return propertyName;
		}

		return null;
	}

	private static interface ValueConverter<T> {

		T convert(JsonNode node);

	}

	private static class JSONArrayConverter implements ValueConverter<Object> {

		private Class<?> componentType;

		private ValueConverter<?> componentValueConverter;

		public JSONArrayConverter(Class<?> arrayComponentType, ValueConverter<?> componentValueConverter) {
			this.componentType = arrayComponentType;
			this.componentValueConverter = componentValueConverter;
		}

		@Override
		public Object convert(JsonNode node) {
			if (node == null) {
				return null;
			}
			if (!node.isArray()) {
				throw new JsonException("JSONArrayConverter cannnot convert none-array json node!");
			}
			int length = node.size();
			Object values = Array.newInstance(componentType, length);
			for (int i = 0; i < length; i++) {
				JsonNode jsonValue = node.get(i);
				Object value = componentValueConverter.convert(jsonValue);
				Array.set(values, i, value);
			}
			return values;
		}

	}

	private static class JsonObjectConverter<T> implements ValueConverter<T> {
		private Class<T> valueType;

		private ObjectMapper context;

		public JsonObjectConverter(Class<T> valueType, ObjectMapper context) {
			this.valueType = valueType;
			this.context = context;
		}

		@Override
		public T convert(JsonNode node) {
			try {
				return context.treeToValue(node, valueType);
			} catch (JsonProcessingException | IllegalArgumentException e) {
				throw new JsonException(e.getMessage(), e);
			}
		}
	}

//	private static class JsonProxyConverter<T> implements ValueConverter<T> {
//		private Class<T> valueType;
//
//		private ClassLoader classloader;
//
//		public JsonProxyConverter(Class<T> valueType, ClassLoader classloader) {
//			this.valueType = valueType;
//			this.classloader = classloader;
//		}
//
//		@Override
//		public T convert(JsonNode node) {
//			return (T) Proxy.newProxyInstance(classloader, new Class<?>[] { valueType },
//					new JSONProxyAccesser(node, valueType, classloader));
//		}
//	}

	private static class PropertyConverter {

		public final String PROPERTY_NAME;

		@SuppressWarnings("rawtypes")
		private final ValueConverter converter;

		@SuppressWarnings("rawtypes")
		public PropertyConverter(String propertyName, ValueConverter converter) {
			this.PROPERTY_NAME = propertyName;
			this.converter = converter;
		}

		Object convert(JsonNode node) {
			return converter.convert(node);
		}
	}

}
